package com.tiancheng.trade.settlement.service.settlement;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tiancheng.trade.commom.core.utils.DateTimeUtil;
import com.tiancheng.trade.settlement.enums.*;
import com.tiancheng.trade.settlement.mapper.payment.PaymentFlowDetailMapper;
import com.tiancheng.trade.settlement.model.settlement.SettleDetail;
import com.tiancheng.trade.settlement.model.settlement.SettleSummary;
import com.tiancheng.trade.settlement.util.JsonUtil;
import com.tiancheng.trade.settlement.vo.payment.PaymentFlowDetailVO;
import com.tiancheng.trade.settlement.vo.payment.RefundFlowDetailVO;
import com.tiancheng.trade.settlement.vo.settlement.admin.SettleMarkBatchVO;
import com.tiancheng.trade.settlement.vo.settlement.admin.SettleQueryVo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 对账相关业务实现
 *
 * @author kellen
 * @date 2021/3/31
 */
@Slf4j
@Service
public class SettleCheckService {
    @Autowired
    private ISettleSummaryService settleSummaryService;
    @Autowired
    private ISettleDetailService checkDetailService;
    @Autowired
    private PaymentFlowDetailMapper flowDetailMapper;
    @Autowired
    private RedissonClient redissonClient;

    /**
     * 自动清分对账
     * @param date 对账日期
     */
    @Transactional
    public void clearCheck(LocalDate date) {
        log.info("自动清分对账任务开始执行：{}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        // 同时刻不允许有多个任务处理相同数据
        RLock lock = redissonClient.getLock("SETTLEMENT_SETTLE_AUTO_CHECK");
        boolean isLock;
        try {
            isLock = lock.tryLock(30L, TimeUnit.MINUTES);
            if (!isLock) {
                log.error("自动清分对账任务执行结果：锁失败！");
                return;
            }

            // 1-汇集需要执行对账日期
            Set<LocalDate> checkDateList = new HashSet<>(); // 对账日期列表
            if(null!=date){
                checkDateList.add(date);
            }else {
                checkDateList.add(LocalDate.now().minusDays(1)); // 未对账的日期
            }
            QueryWrapper<SettleSummary> summaryQueryWrapper = new QueryWrapper<>();
            summaryQueryWrapper.eq("data_status", CheckSummaryDataStatusEnum.NEED_RECHECK.getCode());
            List<SettleSummary> settleSummaries = settleSummaryService.list(summaryQueryWrapper);
            List<String> checkCodes = new ArrayList<>(1000); // 此前回盘文件未返回的对账汇总编码
            if (settleSummaries.size() > 0) {
                for (SettleSummary settleSummary : settleSummaries) {
                    checkDateList.add(settleSummary.getActualClearDate()); // 此前回盘文件未返回的实际清分时间
                    checkCodes.add(settleSummary.getCheckCode());
                }
            }
            summaryQueryWrapper = null;
            settleSummaries.clear();
            settleSummaries = null;

            // 2-分批先处理支付清分明细
            Map<String, SettleSummary> settleSummaryMap = new HashMap<>(1000); // 收集“商户-日期-渠道”维度对账汇总结果
            Set<Long> fdIdSet = new HashSet<>(); // 退款明细中不需要记录的订单明细
            List<PaymentFlowDetailVO> flowDetailVOS;
            log.info("开始对账，查询对账记录日期为：{}，", JsonUtil.toJson(checkDateList));
            long idOffset = 0L;
            do {
//                flowDetailVOS = flowDetailMapper.listCheckDetail(checkDateList, idOffset).stream().collect(
//                        Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(PaymentFlowDetailVO::getFdId))), ArrayList::new)
//                );
                flowDetailVOS = flowDetailMapper.listCheckDetail(checkDateList, idOffset);
                log.info("获取到应对账支付流水记录：{}条，", flowDetailVOS.size());
                if (flowDetailVOS.size() < 1) break;

                idOffset = flowDetailVOS.stream().mapToLong(PaymentFlowDetailVO::getFdId).max().orElse(0L);

                handlePayCheck(flowDetailVOS, settleSummaryMap, fdIdSet);
            } while (flowDetailVOS.size() > 999);
            flowDetailVOS.clear();
            flowDetailVOS = null;
            if (settleSummaryMap.size() < 1) return;

            settleSummaryMap.forEach((s, settleSummary) -> {
                log.info("本次商户对账汇总记录，商户：{},mapKey:{},汇总：{}",settleSummary.getMerchantCode(),s,JsonUtil.toJson(settleSummary));
            });

            // 3-分批处理退款清分明细
            idOffset = 0L;
            List<RefundFlowDetailVO> refundDetails;
            do {
                /*refundDetails = flowDetailMapper.listCheckRefundDetail(checkDateList, idOffset).stream().collect(
                        Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(RefundFlowDetailVO::getRfdId))), ArrayList::new)
                );*/
                refundDetails = flowDetailMapper.listCheckRefundDetail(checkDateList, idOffset);
                log.info("获取到应对账退款记录：{}条", refundDetails.size());
                if (refundDetails.size() < 1) break;

                idOffset = refundDetails.stream().mapToLong(RefundFlowDetailVO::getRfdId).max().orElse(0L);

                handleRefundCheck(refundDetails, settleSummaryMap, fdIdSet);
            } while (refundDetails.size() > 999);
            fdIdSet.clear();
            fdIdSet = null;
            refundDetails.clear();
            refundDetails = null;
            checkDateList.clear();
            checkDateList = null;

            // 4-处理入库结果
            handleCheckResult(settleSummaryMap, checkCodes);
        } catch (Exception e) {
            log.error("自动清分对账任务执行执行失败：{},Exception:", e.getMessage(),e);
            throw new RuntimeException("自动清分对账任务执行执行失败", e);
        } finally {
            if (lock.isLocked()) {
                lock.unlock();
            }
        }
    }

    /**
     * 更新对账数据
     * 原结果存在，更新；原结果不存在，执行对账
     *
     * @param settleQueryVo SettleQueryVo
     * @return String
     */
    @Transactional
    public String doUpdate(SettleQueryVo settleQueryVo) {
        log.info("手动清分对账任务开始执行：{}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        // 同时刻不允许有多个任务处理相同数据
        RLock lock = redissonClient.getLock("SETTLEMENT_SETTLE_CHECK_BY_OPERATION");
        boolean isLock;
        try {
            isLock = lock.tryLock(30L, TimeUnit.MINUTES);
            if (!isLock) {
                log.error("手动清分对账任务执行结果：锁失败！");
                return "FAIL，手动清分对账任务执行结果：锁失败！";
            }

            QueryWrapper<SettleSummary> summaryQueryWrapper = new QueryWrapper<>();
            summaryQueryWrapper.eq("actual_clear_date", settleQueryVo.getActualClearDate());
            summaryQueryWrapper.eq("merchant_code", settleQueryVo.getMerchantCode());
            summaryQueryWrapper.eq("pay_channel", settleQueryVo.getPayChannel());
            summaryQueryWrapper.in("data_status", new ArrayList<Integer>() {{
                add(1);
                add(2);
            }});
            SettleSummary settleSummary = settleSummaryService.getOne(summaryQueryWrapper);
            List<String> checkCodes = new ArrayList<>();
            if (null != settleSummary) checkCodes.add(settleSummary.getCheckCode());
            summaryQueryWrapper = null;
            settleSummary = null;

            // 分批先处理支付清分明细
            Map<String, SettleSummary> settleSummaryMap = new HashMap<>(1000); // 收集“商户-日期-渠道”维度对账汇总结果
            Set<Long> fdIdSet = new HashSet<>(); // 退款明细中不需要记录的订单明细
            List<PaymentFlowDetailVO> flowDetailVOS;
            long idOffset = 0L;
            do {
                flowDetailVOS = flowDetailMapper.listUpdateCheckDetail(settleQueryVo, idOffset).stream().collect(
                        Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(PaymentFlowDetailVO::getFdId))), ArrayList::new)
                );

                if (flowDetailVOS.size() < 1) break;

                idOffset = flowDetailVOS.stream().mapToLong(PaymentFlowDetailVO::getFdId).max().orElse(0L);

                handlePayCheck(flowDetailVOS, settleSummaryMap, fdIdSet);
            } while (flowDetailVOS.size() > 999);
            flowDetailVOS.clear();
            flowDetailVOS = null;

            if (settleSummaryMap.size() < 1) return "FAIL，对账明细不存在！";

            // 分批处理退款清分明细
            idOffset = 0L;
            List<RefundFlowDetailVO> refundDetails;
            do {
                refundDetails = flowDetailMapper.listUpdateCheckRefundDetail(settleQueryVo, idOffset).stream().collect(
                        Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(RefundFlowDetailVO::getRfdId))), ArrayList::new)
                );

                if (refundDetails.size() < 1) break;

                idOffset = refundDetails.stream().mapToLong(RefundFlowDetailVO::getRfdId).max().orElse(0L);

                handleRefundCheck(refundDetails, settleSummaryMap, fdIdSet);
            } while (refundDetails.size() > 999);
            fdIdSet.clear();
            fdIdSet = null;
            refundDetails.clear();
            refundDetails = null;

            // 处理入库结果
            handleCheckResult(settleSummaryMap, checkCodes);

            return "SUCCESS";
        } catch (Exception e) {
            log.error("手动清分对账任务执行执行失败：{}", e.getMessage());
            throw new RuntimeException("手动清分对账任务执行执行失败", e);
        } finally {
            if (lock.isLocked()) {
                lock.unlock();
            }
        }
    }

    /**
     * 对支付流水对账处理
     *
     * @param flowDetailVOS    List
     * @param settleSummaryMap Map
     * @param fdIdSet          Set
     */
    private void handlePayCheck(List<PaymentFlowDetailVO> flowDetailVOS, Map<String, SettleSummary> settleSummaryMap, Set<Long> fdIdSet) {
        List<SettleDetail> checkDetails = new ArrayList<>(1000); // 收集“子订单”维度交易资金对账明细

        for (PaymentFlowDetailVO flowDetailVO : flowDetailVOS) {
            fdIdSet.add(flowDetailVO.getFdId());
            /*
             * 汇总表处理
             */
            String key = flowDetailVO.getMerchantCode() + "&" + flowDetailVO.getActualClearDate() + "&" + flowDetailVO.getPaymentChannel();
            if (null == settleSummaryMap.get(key)) {
                SettleSummary summary = new SettleSummary();
                summary.setCheckCode(getCheckCode()); // 对账编号
                summary.setActualClearDate(flowDetailVO.getActualClearDate()); // 中台实际清算日期
                summary.setMerchantCode(flowDetailVO.getMerchantCode()); // 中台商户编码
                summary.setMerchantName(flowDetailVO.getMerchantName()); // 商户名称
                summary.setPayChannel(flowDetailVO.getPaymentChannel()); // 交易渠道
                summary.setPayChannelName(PaymentChannelEnum.getDesc(flowDetailVO.getPaymentChannel())); // 交易渠道名称

                // 若成功划付笔数为空，视为回盘文件未返回，数据状态记状态为“有效，但仍需重新对账”，渠道数据记为0
                if (flowDetailVO.getApprSuccessNum() != null) {
                    summary.setDataStatus(CheckSummaryDataStatusEnum.NORMAL.getCode()); // 数据状态
                    summary.setChannelFlowTotalAmount(flowDetailVO.getTargetApprAmount()); // 渠道交易金额总和（分)
                    summary.setChannelRefundTotalAmount(flowDetailVO.getRefundAmount()); // 渠道退款金额总和（分）
                    long apprAmount = "SUCCESS".equals(flowDetailVO.getApprStatus()) ? flowDetailVO.getApprAmount() : 0L;
                    long distAmount = "SUCCESS".equals(flowDetailVO.getDistStatus()) ? flowDetailVO.getDistAmount() : 0L;
                    summary.setChannelClearTotalAmount(apprAmount + distAmount); // 渠道清出金额总和（分）
                    summary.setDistAmount(flowDetailVO.getDistAmount());
                } else {
                    summary.setDataStatus(CheckSummaryDataStatusEnum.NEED_RECHECK.getCode());
                    summary.setChannelFlowTotalAmount(0L);
                    summary.setChannelRefundTotalAmount(0L);
                    summary.setChannelClearTotalAmount(0L);
                    summary.setDistAmount(0L);
                }

                summary.setMdFlowTotalAmount(flowDetailVO.getOrderAmount().longValue()); // 中台交易金额总和（分）
                summary.setMdRefundTotalAmount(0L); // 中台退款金额总和（分）
                summary.setMdClearTotalAmount(0L); // 中台清出金额总和（分）

                summary.setDifferentAmountFlow(0L); // 交易差异金额（分）
                summary.setDifferentAmountRefund(0L); // 退款差异金额（分）
                summary.setDifferentAmountClear(0L); // 清出差异金额（分）

                settleSummaryMap.put(key, summary);
            } else {
                settleSummaryMap.get(key).setMdFlowTotalAmount(settleSummaryMap.get(key).getMdFlowTotalAmount() + flowDetailVO.getOrderAmount().longValue()); // 中台交易金额总和（分）
            }
            /*
             * 明细表处理
             */
            SettleDetail detail = new SettleDetail();
            detail.setCheckCode(settleSummaryMap.get(key).getCheckCode()); // 对账编号
            detail.setOrderId(flowDetailVO.getSubOrderId().isEmpty() ? "" : flowDetailVO.getSubOrderId().substring(0, flowDetailVO.getSubOrderId().length() - 2)); // 主订单号，使用子订单号减去后两位得到
            detail.setSubOrderId(flowDetailVO.getSubOrderId()); // 子订单号
            detail.setPaymentId(flowDetailVO.getPaymentId()); // 支付订单号
            detail.setFlowId(flowDetailVO.getFlowId()); // 支付流水号
            detail.setSubOrderFlowId(flowDetailVO.getSubOrderFlowId()); // 子订单流水号
            detail.setCheckDesc(flowDetailVO.getTargetClearDate().isBefore(flowDetailVO.getActualClearDate()) ?
                    CheckDetailCheckDescEnum.HISTORY_CLEAR.getCode() : CheckDetailCheckDescEnum.TARGET_CLEAR.getCode()); // 对账数据描述
            // 若划付文件回盘文件返回，明细成功，才标注为“无差异”
            if (PaymentFlowDetailSettleStatusEnum.SUCCESS.getCode().equals(flowDetailVO.getClearStatus())
                    && flowDetailVO.getApprSuccessNum() != null) {
                detail.setDifferentType(CheckDetailDifferentTypeEnum.NORMAL.getCode()); // 差异类型
                detail.setDifferentAmount(0L); // 差异金额
                detail.setClearAmount(flowDetailVO.getOrderAmount().longValue()); // 清出金额
            } else {
                detail.setDifferentType(CheckDetailDifferentTypeEnum.MD_OWN.getCode());
                detail.setDifferentAmount(flowDetailVO.getOrderAmount().longValue());
                detail.setClearAmount(0L);
            }
            detail.setTargetClearDate(flowDetailVO.getTargetClearDate()); // 应清分日期
            detail.setActualClearDate(flowDetailVO.getActualClearDate()); // 实际清分日期

            checkDetails.add(detail);
        }

        // 明细表记录明细
        if (!checkDetails.isEmpty()) {
            checkDetailService.saveBatch(checkDetails, checkDetails.size());
        }
    }

    /**
     * 对退款流水对账处理
     *
     * @param refundDetails    List
     * @param settleSummaryMap Map
     * @param fdIdSet          Set
     */
    private void handleRefundCheck(List<RefundFlowDetailVO> refundDetails, Map<String, SettleSummary> settleSummaryMap, Set<Long> fdIdSet) {
        List<SettleDetail> checkDetails = new ArrayList<>(1000); // 收集“子订单”维度交易资金对账明细

        for (RefundFlowDetailVO refundDetail : refundDetails) {
            /*
             * 汇总表处理
             */
            String key = refundDetail.getMerchantCode() + "&" + refundDetail.getActualClearDate() + "&" + refundDetail.getPaymentChannel();
            if(null==settleSummaryMap.get(key)){
                log.error("获取对账总记录为空，key：{}",key);
            }
            if(null==settleSummaryMap.get(key).getMdRefundTotalAmount()){
                log.error("获取对账总记录,中台退款金额总和为空，key：{}，settleSummaryMap：{}",key,JsonUtil.toJson(settleSummaryMap.get(key)));
            }
            settleSummaryMap.get(key).setMdRefundTotalAmount(settleSummaryMap.get(key).getMdRefundTotalAmount() + refundDetail.getRefundDetailAmount()+refundDetail.getRefundPlatformDiscountAmount()); // 中台退款金额总和（分）

            if (fdIdSet.contains(refundDetail.getFdId())) continue; // 若先前已记录明细，本次明细记录排除该订单

            /*
             * 明细表处理
             */
            SettleDetail detail = new SettleDetail();
            detail.setCheckCode(settleSummaryMap.get(key).getCheckCode()); // 对账编号
            detail.setOrderId(refundDetail.getSubOrderId().isEmpty() ? "" : refundDetail.getSubOrderId().substring(0, refundDetail.getSubOrderId().length() - 2)); // 主订单号
            detail.setSubOrderId(refundDetail.getSubOrderId()); // 子订单号
            detail.setPaymentId(refundDetail.getPaymentId()); // 支付订单号
            detail.setFlowId(refundDetail.getFlowId()); // 支付流水号
            detail.setSubOrderFlowId(refundDetail.getSubOrderFlowId()); // 子订单流水号
            detail.setCheckDesc(refundDetail.getTargetClearDate().isBefore(refundDetail.getActualClearDate()) ?
                    CheckDetailCheckDescEnum.HISTORY_CLEAR.getCode() : CheckDetailCheckDescEnum.TARGET_CLEAR.getCode()); // 对账数据描述
            if (PaymentFlowDetailSettleStatusEnum.SUCCESS.getCode().equals(refundDetail.getClearStatus())) {
                detail.setDifferentType(CheckDetailDifferentTypeEnum.NORMAL.getCode()); // 差异类型
                detail.setDifferentAmount(0L); // 差异金额
                detail.setClearAmount(refundDetail.getOrderAmount().longValue()); // 清出金额
            } else {
                detail.setDifferentType(CheckDetailDifferentTypeEnum.MD_OWN.getCode());
                detail.setDifferentAmount(refundDetail.getOrderAmount().longValue());
                detail.setClearAmount(0L);
            }
            detail.setTargetClearDate(refundDetail.getTargetClearDate()); // 应清分日期
            detail.setActualClearDate(refundDetail.getActualClearDate()); // 实际清分日期

            checkDetails.add(detail);
        }

        // 明细表记录
        if (!checkDetails.isEmpty()) {
            checkDetailService.saveBatch(checkDetails, checkDetails.size());
        }
    }

    /**
     * 处理入库结果
     *
     * @param settleSummaryMap Map
     * @param checkCodes       List
     */
    private void handleCheckResult(Map<String, SettleSummary> settleSummaryMap, List<String> checkCodes) {
        if (!settleSummaryMap.isEmpty()) {
            // 计算差异
            settleSummaryMap.forEach((k, v) -> {
                v.setMdClearTotalAmount(v.getMdFlowTotalAmount() - v.getMdRefundTotalAmount()); // 中台清出金额总和（分）

                v.setDifferentAmountFlow(Math.abs(v.getChannelFlowTotalAmount() - v.getMdFlowTotalAmount())); // 交易差异金额（分）
                v.setDifferentAmountRefund(Math.abs(v.getChannelRefundTotalAmount() - v.getMdRefundTotalAmount())); // 退款差异金额（分）
                v.setDifferentAmountClear(Math.abs(v.getChannelClearTotalAmount() - v.getMdClearTotalAmount())); // 清出差异金额（分）

                v.setCheckResult(v.getDifferentAmountClear() > 0 ?
                        CheckSummaryCheckResultEnum.ABNORMAL.getCode() : CheckSummaryCheckResultEnum.NORMAL.getCode()); // 对账结果
            });

            // 把原来对账记录删除（汇总表置为“无效”，明细表直接物理删除）
            if (!checkCodes.isEmpty()) {
                log.info("作废原有对账记录，对账编码：{}",JsonUtil.toJson(checkCodes));
                settleSummaryService.updateBatch(checkCodes, CheckSummaryDataStatusEnum.ABNORMAL.getCode());
                QueryWrapper<SettleDetail> removeCondition = new QueryWrapper<>();
                removeCondition.in("check_code", checkCodes);
                checkDetailService.remove(removeCondition);
            }

            settleSummaryService.saveBatch(settleSummaryMap.values(), settleSummaryMap.values().size());
        }
    }

    /**
     * 获取对账编码
     *
     * @return
     */
    public String getCheckCode() {

        String incrTime = DateTimeUtil.format(LocalDateTime.now(), "yyMMddHH");
        RAtomicLong rAtomicLong = redissonClient.getAtomicLong("settlementSettleAuto" + incrTime);
        long incr = rAtomicLong.incrementAndGet();
        // 如果是当前小时内第一个取号，就给key添加隔1小时失效时间
        if (incr == 1) {
            rAtomicLong.expireAsync(1, TimeUnit.HOURS);
        }
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + String.format("%05d", incr);
    }

//    /**
//     * 根据子订单号获取主订单号
//     *
//     * @param subOrderIds List
//     * @return Map
//     */
//    private Map<String, String> getOrderIdMap(List<String> subOrderIds) {
//        QueryWrapper<SubOrder> subOrderQueryWrapper = new QueryWrapper<>();
//        subOrderQueryWrapper.in("sub_order_id", subOrderIds);
//        return subOrderMapper.selectList(subOrderQueryWrapper)
//                .stream().collect(Collectors.toMap(SubOrder::getSubOrderId, SubOrder::getOrderId));
//    }


    /**
     * 批量备注
     *
     * @param markBatchVO SettleMarkBatchVO
     * @return Boolean
     */
    public Boolean markBatch(SettleMarkBatchVO markBatchVO) {
        try {
            String desc = markBatchVO.getMarkDesc();
            List<SettleDetail> details = markBatchVO.getCheckDetailIds().stream().map(d -> new SettleDetail() {{
                setId(d);
                setExceptionDesc(desc);
            }}).collect(Collectors.toList());
            checkDetailService.updateBatchById(details, details.size());

            return true;
        } catch (Exception e) {
            log.error("批量备注异常：{}", e.getMessage());
        }
        return false;
    }
}
